package com.gnetek.netty.client;

import com.gnetek.netty.handler.EchoClientHandler;
import com.gnetek.netty.interf.NettyController;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.InetSocketAddress;

/**
 * @Description 客户端
 *  * 1.为初始化客户端，创建一个Bootstrap实例
 *  * 2.为进行事件处理分配了一个NioEventLoopGroup实例，其中事件处理包括创建新的连接以及处理入站和出站数据；
 *  * 3.当连接被建立时，一个EchoClientHandler实例会被安装到（该Channel的一个ChannelPipeline中；
 *  * 4.在一切都设置完成后，调用Bootstrap.connect()方法连接到远程节点。
 * @Author Darren Huang
 * @Date 2022-11-08 10:13
 */
public class EchoClient {

    private static Logger log = LoggerFactory.getLogger(EchoClient.class);

    private static EchoClient echoClient = new EchoClient();

    public static EchoClient getInstance() {
        return echoClient;
    }

    private SocketChannel channel;

    public SocketChannel getChannel(){
        return channel;
    }

    private EventLoopGroup group;

    private boolean isConnent = false;

    public boolean isConnent(){
        return isConnent;
    }

    /**
     * 第一次使用时调用，创建连接
     * Netty用于接收客户端请求的线程池职责如下。
     * * （1）接收客户端TCP连接，初始化Channel参数；
     * * （2）将链路状态变更事件通知给ChannelPipeline
     * @param host
     * @param port
     */
    public static boolean create(String host, int port, NettyController nettyCtrl) {
        if(echoClient.group==null) {
            echoClient.group = new NioEventLoopGroup();
            try {
                Bootstrap b = new Bootstrap();
                b.group(echoClient.group)
                        .channel(NioSocketChannel.class)
                        .remoteAddress(new InetSocketAddress(host, port))
                        .handler(new ChannelInitializer<SocketChannel>() {
                            @Override
                            protected void initChannel(SocketChannel socketChannel) throws Exception {
                                socketChannel.pipeline().addLast(new EchoClientHandler(nettyCtrl));
                            }
                        });
                //绑定端口
                ChannelFuture f = b.connect().sync();
//            f.channel().closeFuture().sync();//会发生阻塞,需要注释掉
                echoClient.channel = (SocketChannel) f.sync().channel();
                echoClient.isConnent = true;
                log.info("创建连接成功，已经连接上服务器。");
            } catch (Exception e) {
                log.info("创建连接失败。");
                log.error(e.getMessage());
                try {
                    echoClient.group.shutdownGracefully().sync();
                }catch (Exception e1){
                    log.error(e1.getMessage());
                }finally {
                    echoClient.group = null;
                    echoClient.channel = null;
                    echoClient.isConnent = false;
                }
            }
        }else {
            log.info("连接已存在。");
        }
        return echoClient.isConnent;
    }

    /**
     * 关闭连接
     */
    public static boolean close() {
        boolean flag = true;
        if(echoClient.group!=null) {
            try {
                echoClient.group.shutdownGracefully().sync();
                log.info("断开连接成功。");
            } catch (InterruptedException e) {
                log.info("断开连接失败。");
                log.error(e.getMessage());
                flag = false;
            }finally {
                echoClient.group = null;
                echoClient.channel = null;
                echoClient.isConnent = false;
            }
        }else {
            log.info("没有连接需要断开。");
        }
        return flag;
    }

    /**
     * 发送十六进制字符串命令
     * @param hexCmd
     */
    public void sendHexCmd(String hexCmd){
        channel.writeAndFlush(Unpooled.copiedBuffer(ByteBufUtil.decodeHexDump(hexCmd)));
    }
}
